/**
 * 创建一个scroller的dom
 */
import {throttle} from 'lodash'
import erd from "element-resize-detector";
import elementResizeDetectorMaker from "element-resize-detector";

class Scroller {
  private readonly targetTableWrapperEl: any;
  private fullwidth: boolean;
  private readonly mode: string;
  dom: HTMLDivElement;
  private bar: HTMLDivElement;
  private thumb: HTMLDivElement;
  private readonly checkIsScrollBottom: any;
  private readonly syncDestroyHandler: () => void;
  private detector: elementResizeDetectorMaker.Erd;

  /**
   * 给tableBody创建一个scroller
   * @param {Element} targetTableWrapperEl
   * @param {string} mode 模式，可选值为'hover'或者'always'
   * @param bottom 距离底部的距离
   * @param zIndex z-index值
   */
  constructor(targetTableWrapperEl, {mode = 'hover', bottom = 0, zIndex = 3}) {
    if (!targetTableWrapperEl) {
      throw new Error('need have table element')
    }
    this.targetTableWrapperEl = targetTableWrapperEl
    this.fullwidth = false
    this.mode = mode

    /**
     * 创建相关dom
     */
    const scroller = document.createElement('div')
    scroller.classList.add('el-scrollbar')
    scroller.style.height = '12px'
    scroller.style.position = 'fixed'
    scroller.style.bottom = bottom + 'px'
    scroller.style.zIndex = String(zIndex)

    this.dom = scroller
    this.resetScroller()

    const bar = document.createElement('div')
    bar.classList.add('el-scrollbar__bar', 'is-horizontal')
    this.bar = bar
    scroller.appendChild(bar)

    const thumb = document.createElement('div')
    thumb.classList.add('el-scrollbar__thumb')
    bar.appendChild(thumb)
    this.thumb = thumb

    /**
     * 初始化配置
     */
    const instance = this
    this.checkIsScrollBottom = throttle(() => {
        const {scrollWidth, width} = targetTableWrapperEl.getBoundingClientRect()
        if (width <= scrollWidth) {
          instance.hideScroller()
        } else {
          // 需要重新设置一次当前宽度
          instance.resetBar(false)

          // 显示当前的bar
          instance.showScroller()
        }
      }
      , 1000 / 60)
    document.addEventListener('scroll', this.checkIsScrollBottom) // 全局判断是否需要显示scroller

    // 自动同步,table => scroller
    targetTableWrapperEl.addEventListener('scroll', throttle(() => {
      instance.resetThumbPosition()
    }, 1000 / 60))

    // 自动同步 scroller => table
    this.syncDestroyHandler = this.initScrollSyncHandler()

    // 监听table的dom变化，自动重新设置
    this.detector = erd();
    this.detector.listenTo(targetTableWrapperEl, () => {
      setTimeout(() => {
        instance.resetBar()
        instance.resetScroller()
        instance.resetThumbPosition()
        instance.checkIsScrollBottom()
      })
    })
    setTimeout(() => {
      instance.resetBar()
      instance.resetScroller()
      instance.resetThumbPosition()
      instance.checkIsScrollBottom()
    }, 200)
  }

  /**
   * 自动设置Bar
   * @param {boolean} changeScrollerVisible 是否开启自动设置滚动条显示与否
   */
  resetBar(changeScrollerVisible = true) {
    const {targetTableWrapperEl} = this
    const widthPercentage = (targetTableWrapperEl.clientWidth * 100 / targetTableWrapperEl.scrollWidth)
    const thumbWidth = Math.min(widthPercentage, 100)
    this.thumb.style.width = `${thumbWidth}%`

    this.fullwidth = thumbWidth >= 100

    if (changeScrollerVisible) {
      if (this.fullwidth) {
        this.hideScroller()
      } else {
        this.checkIsScrollBottom()
      }
    }
  }

  resetThumbPosition() {
    this.thumb.style.transform = `translateX(${this.moveX}%)`
  }

  resetScroller() {
    const {targetTableWrapperEl, dom} = this
    const boundingClientRect = targetTableWrapperEl.getBoundingClientRect()
    dom.style.left = boundingClientRect.left + 'px'
    dom.style.width = boundingClientRect.width + 'px'
  }

  get moveX() {
    const {targetTableWrapperEl} = this
    return ((targetTableWrapperEl.scrollLeft * 100) / targetTableWrapperEl.clientWidth)
  }

  /**
   * 让scroller的拖动行为和table的同步
   * 处理类似element-ui的拖拽处理
   */
  initScrollSyncHandler() {
    let cursorDown = false
    let tempClientX = 0
    let rate = 1

    const {thumb, targetTableWrapperEl, bar} = this

    const getRate = () => {
      // 计算一下变换比例，拖拽走的是具体数字，但是这个实际上应该是按照比例变的
      return bar.offsetWidth / thumb.offsetWidth
    }

    const mouseMoveDocumentHandler = throttle(
      /** @param {MouseEvent} e */
      (e) => {
        if (!cursorDown) {
          return
        }
        const {clientX} = e
        const offset = clientX - tempClientX
        const originTempClientX = tempClientX
        tempClientX = clientX

        const tempScrollLeft = targetTableWrapperEl.scrollLeft
        targetTableWrapperEl.scrollLeft += offset * rate
        if (tempScrollLeft === targetTableWrapperEl.scrollLeft) {
          tempClientX = originTempClientX
        }
      }, 1000 / 60)

    const mouseUpDocumentHandler = () => {
      cursorDown = false
      document.removeEventListener('mousemove', mouseMoveDocumentHandler)
      document.removeEventListener('mouseup', mouseUpDocumentHandler)
      document.onselectstart = null
    }

    /**
     * 拖拽处理
     * @param {MouseEvent} e
     */
    const startDrag = (e) => {
      e.stopImmediatePropagation()
      cursorDown = true
      document.addEventListener('mousemove', mouseMoveDocumentHandler)
      document.addEventListener('mouseup', mouseUpDocumentHandler)
      document.onselectstart = () => false
    }

    thumb.onmousedown = (e) => {
      // prevent click event of right button
      if (e.ctrlKey || e.button === 2) {
        return
      }

      const {clientX} = e
      tempClientX = clientX
      rate = getRate()
      startDrag(e)
    }

    /**
     * 点击槽快速移动
     * @param {PointerEvent} e
     */
    bar.onclick = (e) => {
      const {target} = e
      if (target !== bar) {
        return
      }
      rate = getRate()
      const {clientX} = e
      let offset: number
      const thumbPosition = thumb.getBoundingClientRect()
      if (thumbPosition.left >= clientX) {
        offset = (clientX - thumbPosition.left)
      } else {
        offset = clientX - thumbPosition.left - thumbPosition.width
      }

      const targetScrollLeft = targetTableWrapperEl.scrollLeft + offset * rate
      targetTableWrapperEl.scrollTo({
        left: targetScrollLeft,
        behavior: 'smooth'
      })
    }

    return () => {
      document.removeEventListener('mouseup', mouseUpDocumentHandler)
    }
  }

  /**
   * 显示整体
   */
  showScroller() {
    if (!this.fullwidth) {
      this.dom.style.display = 'initial'
    }
  }

  /**
   * 隐藏整体
   */
  hideScroller() {
    if (this.mode === 'force') {
      this.dom.style.display = 'initial'
    } else {
      this.dom.style.display = 'none'
    }
  }

  /**
   * 显示滚动条
   */
  showBar() {
    this.bar.style.opacity = String(0.7)
  }

  /**
   * 隐藏滚动条
   */
  hideBar() {
    if (this.mode === 'force') {
      this.bar.style.opacity = String(0.7)
    } else {
      this.bar.style.opacity = String(0)
    }
  }

  destroy() {
    this.detector.uninstall(this.targetTableWrapperEl);
    document.removeEventListener('scroll', this.checkIsScrollBottom)
    this.syncDestroyHandler()
  }
}

/** @type {Vue.DirectiveOptions} */
export const horizontalScroll = {
  inserted(el, binding) {
    const {value} = binding
    const {mode = 'hover'} = value
    const tableBodyWrapper = el.querySelector('.el-table__body-wrapper')
    const scroller = new Scroller(tableBodyWrapper, value)

    el.appendChild(scroller.dom)
    el.horizontalScroll = scroller

    if (mode === 'hover') {
      el.addEventListener('mouseover', scroller.showBar.bind(scroller))
      el.addEventListener('mouseleave', scroller.hideBar.bind(scroller))
    } else {
      scroller.showBar()
    }
  },
  unbind(el) {
    el.horizontalScroll.destroy()
  }
}

/**
 * 插件
 */
export const Plugin = {
  install(Vue) {
    Vue.directive('horizontalScroll', horizontalScroll)
  }
}

export default Plugin
